//#if not omit-kvvfs
/*
  2025-11-21

  The author disclaims copyright to this source code.  In place of a
  legal notice, here is a blessing:

  *   May you do good and not evil.
  *   May you find forgiveness for yourself and forgive others.
  *   May you share freely, never taking more than you give.

  ***********************************************************************

  This file houses the "kvvfs" pieces of the JS API.

  Main project home page: https://sqlite.org

  Documentation home page: https://sqlite.org/wasm
*/
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
  'use strict';
  /* We unregister the kvvfs VFS from Worker threads later on. */
  const util = sqlite3.util,
        capi = sqlite3.capi,
        wasm = sqlite3.wasm,
        sqlite3_kvvfs_methods = capi.sqlite3_kvvfs_methods,
        pKvvfs = sqlite3.capi.sqlite3_vfs_find("kvvfs");
  delete capi.sqlite3_kvvfs_methods /* this is JS plumbing, not part
                                       of the public API */;
  if( !pKvvfs ) return /* built without kvvfs */;

  if( !util.isUIThread() ){
    /* One test currently relies on this VFS not being visible
       in Workers. If we add generic object storage, we can
       retain this VFS in Workers. */
    capi.sqlite3_vfs_unregister(pKvvfs);
    return;
  }

    /**
       Internal helper for sqlite3_js_kvvfs_clear() and friends.
       Its argument should be one of ('local','session',"").
    */
  const __kvfsWhich = function(which){
    const rc = Object.create(null);
    rc.prefix = 'kvvfs-'+which;
    rc.stores = [];
    if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage);
    if('local'===which || ""===which) rc.stores.push(globalThis.localStorage);
    return rc;
  };

  /**
     Clears all storage used by the kvvfs DB backend, deleting any
     DB(s) stored there. Its argument must be either 'session',
     'local', or "". In the first two cases, only sessionStorage
     resp. localStorage is cleared. If it's an empty string (the
     default) then both are cleared. Only storage keys which match
     the pattern used by kvvfs are cleared: any other client-side
     data are retained.

     This function is only available in the main window thread.

     Returns the number of entries cleared.
  */
  capi.sqlite3_js_kvvfs_clear = function(which=""){
    let rc = 0;
    const kvWhich = __kvfsWhich(which);
    kvWhich.stores.forEach((s)=>{
      const toRm = [] /* keys to remove */;
      let i;
      for( i = 0; i < s.length; ++i ){
        const k = s.key(i);
        if(k.startsWith(kvWhich.prefix)) toRm.push(k);
      }
      toRm.forEach((kk)=>s.removeItem(kk));
      rc += toRm.length;
    });
    return rc;
  };

  /**
     This routine guesses the approximate amount of
     window.localStorage and/or window.sessionStorage in use by the
     kvvfs database backend. Its argument must be one of ('session',
     'local', ""). In the first two cases, only sessionStorage
     resp. localStorage is counted. If it's an empty string (the
     default) then both are counted. Only storage keys which match
     the pattern used by kvvfs are counted. The returned value is
     twice the "length" value of every matching key and value,
     noting that JavaScript stores each character in 2 bytes.

     The returned size is not authoritative from the perspective of
     how much data can fit into localStorage and sessionStorage, as
     the precise algorithms for determining those limits are
     unspecified and may include per-entry overhead invisible to
     clients.
  */
  capi.sqlite3_js_kvvfs_size = function(which=""){
    let sz = 0;
    const kvWhich = __kvfsWhich(which);
    kvWhich.stores.forEach((s)=>{
      let i;
      for(i = 0; i < s.length; ++i){
        const k = s.key(i);
        if(k.startsWith(kvWhich.prefix)){
          sz += k.length;
          sz += s.getItem(k).length;
        }
      }
    });
    return sz * 2 /* because JS uses 2-byte char encoding */;
  };

  const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack;
  const pstack = wasm.pstack;
  const kvvfsStorage = (zClass)=>((115/*=='s'*/===wasm.peek(zClass))
                                  ? sessionStorage : localStorage);

  { /* Override native sqlite3_kvvfs_methods */
    const kvvfsMethods = new sqlite3_kvvfs_methods(
      /* Wraps the static sqlite3_api_methods singleton */
      wasm.exports.sqlite3__wasm_kvvfs_methods()
    );
    try{
      /**
         Implementations for members of the object referred to by
         sqlite3__wasm_kvvfs_methods(). We swap out the native
         implementations with these, which use JS Storage for their
         backing store.

         The interface docs for these methods are in
         src/os_kv.c:kvstorageRead(), kvstorageWrite(), and
         kvstorageDelete().
      */
      for(const e of Object.entries({
        xRead: (zClass, zKey, zBuf, nBuf)=>{
          const stack = pstack.pointer,
                astack = wasm.scopedAllocPush();
          try {
            const zXKey = kvvfsMakeKey(zClass,zKey);
            if(!zXKey) return -3/*OOM*/;
            const jKey = wasm.cstrToJs(zXKey);
            const jV = kvvfsStorage(zClass).getItem(jKey);
            if(!jV) return -1;
            const nV = jV.length /* We are relying 100% on v being
                                    ASCII so that jV.length is equal
                                    to the C-string's byte length. */;
            if(nBuf<=0) return nV;
            else if(1===nBuf){
              wasm.poke(zBuf, 0);
              return nV;
            }
            const zV = wasm.scopedAllocCString(jV);
            if(nBuf > nV + 1) nBuf = nV + 1;
            wasm.heap8u().copyWithin(
              Number(zBuf), Number(zV), wasm.ptr.addn(zV, nBuf,- 1)
            );
            wasm.poke(wasm.ptr.add(zBuf, nBuf, -1), 0);
            return nBuf - 1;
          }catch(e){
            sqlite3.config.error("kvstorageRead()",e);
            return -2;
          }finally{
            pstack.restore(stack);
            wasm.scopedAllocPop(astack);
          }
        },
        xWrite: (zClass, zKey, zData)=>{
          const stack = pstack.pointer;
          try {
            const zXKey = kvvfsMakeKey(zClass,zKey);
            if(!zXKey) return 1/*OOM*/;
            const jKey = wasm.cstrToJs(zXKey);
            kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
            return 0;
          }catch(e){
            sqlite3.config.error("kvstorageWrite()",e);
            return capi.SQLITE_IOERR;
          }finally{
            pstack.restore(stack);
          }
        },
        xDelete: (zClass, zKey)=>{
          const stack = pstack.pointer;
          try {
            const zXKey = kvvfsMakeKey(zClass,zKey);
            if(!zXKey) return 1/*OOM*/;
            kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
            return 0;
          }catch(e){
            sqlite3.config.error("kvstorageDelete()",e);
            return capi.SQLITE_IOERR;
          }finally{
            pstack.restore(stack);
          }
        }
      })){
        kvvfsMethods[kvvfsMethods.memberKey(e[0])] =
          wasm.installFunction(kvvfsMethods.memberSignature(e[0]), e[1]);
      }
    }finally{
      kvvfsMethods.dispose();
    }
  }/* Override native sqlite3_api_methods */

  /**
     After initial refactoring to support the use of arbitrary Storage
     objects (the interface from which localStorage and sessionStorage
     dervie), we will apparently need to override some of the
     associated sqlite3_vfs and sqlite3_io_methods members.

     We can call back into the native impls when needed, but we need
     to override certain operations here to bypass its strict
     db-naming rules (which, funnily enough, are in place because
     they're relevant (only) for this browser-side
     implementation). Apropos: the port to generic objects would also
     make non-persistent kvvfs available in Worker threads and
     non-browser builds.
  */
  const eventualTodo = 1 || {
    vfsMethods:{
      /**
      */
      xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){},
      xDelete: function(pVfs, zName, iSyncFlag){},
      xAccess:function(pProtoVfs, zPath, flags, pResOut){},
      xFullPathname: function(pVfs, zPath, nOut, zOut){},
      xDlOpen: function(pVfs, zFilename){},
      xSleep: function(pVfs,usec){},
      xCurrentTime: function(pVfs,pOutDbl){},
      xCurrentTimeInt64: function(pVfs,pOutI64){},
      xRandomness: function(pVfs, nOut, pOut){
        const heap = wasm.heap8u();
        let i = 0;
        const npOut = Number(pOut);
        for(; i < nOut; ++i) heap[npOut + i] = (Math.random()*255000) & 0xFF;
        return i;
      }
    },

    /**
       kvvfs has separate impls for some of the I/O methods,
       depending on whether it's a db or journal file.
    */
    ioMethods:{
      db:{
        xClose: function(pFile){},
        xRead: function(pFile,pTgt,n,iOff64){},
        xWrite: function(pFile,pSrc,n,iOff64){},
        xTruncate: function(pFile,i64){},
        xSync: function(pFile,flags){},
        xFileControl: function(pFile, opId, pArg){},
        xFileSize: function(pFile,pi64Out){},
        xLock: function(pFile,iLock){},
        xUnlock: function(pFile,iLock){},
        xCheckReservedLock: function(pFile,piOut){},
        xFileControl: function(pFile,iOp,pArg){},
        xSectorSize: function(pFile){},
        xDeviceCharacteristics: function(pFile){}
      },
      jrnl:{
        xClose: todoIOMethodsDb.xClose,
        xRead: function(pFile,pTgt,n,iOff64){},
        xWrite: function(pFile,pSrc,n,iOff64){},
        xTruncate: function(pFile,i64){},
        xSync: function(pFile,flags){},
        xFileControl: function(pFile, opId, pArg){},
        xFileSize: function(pFile,pi64Out){},
        xLock: todoIOMethodsDb.xLock,
        xUnlock: todoIOMethodsDb.xUnlock,
        xCheckReservedLock: todoIOMethodsDb.xCheckReservedLock,
        xFileControl: function(pFile,iOp,pArg){},
        xSectorSize: todoIOMethodsDb.xSectorSize,
        xDeviceCharacteristics: todoIOMethodsDb.xDeviceCharacteristics
      }
    }
  }/*eventualTodo*/;

  if(sqlite3?.oo1?.DB){
    /**
       Functionally equivalent to DB(storageName,'c','kvvfs') except
       that it throws if the given storage name is not one of 'local'
       or 'session'.

       As of version 3.46, the argument may optionally be an options
       object in the form:

       {
         filename: 'session'|'local',
         ... etc. (all options supported by the DB ctor)
       }

       noting that the 'vfs' option supported by main DB
       constructor is ignored here: the vfs is always 'kvvfs'.
    */
    sqlite3.oo1.JsStorageDb = function(
      storageName = sqlite3.oo1.JsStorageDb.defaultStorageName
    ){
      const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...arguments);
      storageName = opt.filename;
      if('session'!==storageName && 'local'!==storageName){
        toss3("JsStorageDb db name must be one of 'session' or 'local'.");
      }
      opt.vfs = 'kvvfs';
      sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
    };
    sqlite3.oo1.JsStorageDb.defaultStorageName = 'session';
    const jdb = sqlite3.oo1.JsStorageDb;
    jdb.prototype = Object.create(sqlite3.oo1.DB.prototype);
    /** Equivalent to sqlite3_js_kvvfs_clear(). */
    jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
    /**
       Clears this database instance's storage or throws if this
       instance has been closed. Returns the number of
       database blocks which were cleaned up.
    */
    jdb.prototype.clearStorage = function(){
      return jdb.clearStorage(this.affirmDbOpen().filename);
    };
    /** Equivalent to sqlite3_js_kvvfs_size(). */
    jdb.storageSize = capi.sqlite3_js_kvvfs_size;
    /**
       Returns the _approximate_ number of bytes this database takes
       up in its storage or throws if this instance has been closed.
    */
    jdb.prototype.storageSize = function(){
      return jdb.storageSize(this.affirmDbOpen().filename);
    };
  }/*sqlite3.oo1.JsStorageDb*/

})/*globalThis.sqlite3ApiBootstrap.initializers*/;
//#endif not omit-kvvfs
